package simplifiedPastry.simulation;

import java.net.InetAddress;
import java.net.SocketException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Random;

import simplifiedPastry.LeafSet;
import simplifiedPastry.Message;
import simplifiedPastry.NodeId;
import simplifiedPastry.Pair;
import simplifiedPastry.ProtocolVariables;
import simplifiedPastry.RoutingTable;
import simplifiedPastry.exceptions.NodeConnectionException;
import simplifiedPastry.exceptions.NodeStateException;
import simplifiedPastry.exceptions.ServerRemoveCountException;

public class ServerImpl extends UnicastRemoteObject implements Server {
	private static final long serialVersionUID = -7185137242576787815L;

	private Registry reg;

	private ArrayList<NodeTest>		myNodes;
	private ArrayList<NodeTest>		removedNodes;
	private ArrayList<NodeTest> 	globalNodes;
	private InetAddress 			address;

	public ServerImpl(InetAddress address) throws RemoteException {
		super();

		try
		{
			reg = LocateRegistry.getRegistry();
			reg.rebind("Server", this);
		}
		catch(RemoteException e) {
			e.printStackTrace();
			throw e;
		}

		myNodes = new ArrayList<NodeTest>(ProtocolVariables.defaultNodesPerServer);
		removedNodes = new ArrayList<NodeTest>(ProtocolVariables.defaultNodesPerServer);
		globalNodes = new ArrayList<NodeTest>(ProtocolVariables.defaultNodesPerServer);

		this.address = address;
	}

	@Override
	public void addGlobalNodes(ArrayList<NodeTest> nodes) throws RemoteException {
		System.out.println("========== Adding " + nodes.size() + " global nodes ==========");
		globalNodes.addAll(nodes);
		System.out.println("========== Added " + nodes.size() + " global nodes  ==========\n");
		
	}

	@Override
	public void addNodes(ArrayList<Pair<NodeId, Integer>> nodes) throws RemoteException {
		System.out.println("========== Adding " + nodes.size() + " nodes ==========");
		for(Pair<NodeId, Integer> pair : nodes) {
			myNodes.add(new NodeTest(pair.first, address, pair.second.intValue()));
		}
		System.out.println("========== Added " + nodes.size() + " nodes  ==========\n");
	}

	@Override
	public void createNewNet() throws RemoteException, SocketException, NodeStateException {
		System.out.println("========== Creating new net ==========");
		myNodes.get(myNodes.size()-1).createNewNet();
		System.out.println("-- Nodes to connect: " + (myNodes.size()-1) + " --");
		
		for(int i = 0; i < myNodes.size()-1; ++i) {
			System.out.println("-- connecting: " + myNodes.get(i).getId() + " to " + myNodes.get(myNodes.size()-1).getId() + " --");
			try {
				myNodes.get(i).connect(address, myNodes.get(myNodes.size()-1).getPort());
			} catch (NodeConnectionException e) {
				System.out.println("-- node: " + myNodes.get(i).getId() + " can't connect --");
			}
		}
		
		
/*		int port = myNodes.get(myNodes.size()-1).getPort();
		int j = 0;
		for(int i = 0; i < myNodes.size()-1; ++i) {
			System.out.println("-- connecting: " + myNodes.get(i).getId() + " to " + myNodes.get(myNodes.size()-1).getId() + " --");
			try {
				if(i == 0)
					myNodes.get(i).connect(address, port);
				else {
					myNodes.get(i).connect(address, myNodes.get(j).getPort());
					j = i;
				}
			}
			catch (NodeConnectionException e)
			{
				System.out.println("-- node: " + myNodes.get(i).getId() + " can't connect --");
			}
		}*/
		System.out.println("========== Nodes connected to new net ==========\n");
	}
	
	@Override
	public void joinAll(InetAddress address, int port) throws RemoteException, NodeStateException, SocketException, NodeConnectionException {
		System.out.println("========== All nodes joining to ==========" + address + ":" + port + "\n========================================");
		for(int i = 0; i < myNodes.size(); ++i) {
			myNodes.get(i).connect(address, port);
		}
		System.out.println("==========   All nodes joined   ==========\n");
	}
	
	public void disconnectAll() throws RemoteException {
		
		System.out.println("==========   Disconnecting all nodes   ==========");
		for(int i = 0; i < myNodes.size(); ++i) {
			myNodes.get(i).disconnect();
		}
		System.out.println("==========   Disconnected all nodes    ==========");
	}

	@Override
	public void removeNodes(Integer number) throws RemoteException, ServerRemoveCountException {
		if(number < 0 || number > myNodes.size()) {
			throw new ServerRemoveCountException();
		}
		System.out.println("========== Removing " + number + " nodes ==========");
		
		Random r = new Random(System.currentTimeMillis());
		int index = 0;
		for(int i = 0; i < number; ++i) {
			index = Math.abs(r.nextInt())%myNodes.size();
			myNodes.get(index).disconnect();
			removedNodes.add(myNodes.get(index));
			myNodes.remove(index);
		}
		System.out.println("==========  Removed " + number + " nodes ==========\n");
	}
	
	public RegistratorData getRegistredData() throws RemoteException {
		System.out.println("========== Collecting registred data   ==========");
		RegistratorData data = new RegistratorData();
		for(int i = 0; i < myNodes.size(); ++i) {
			data.addData(myNodes.get(i));
		}
		for(int i = 0; i < removedNodes.size(); ++i) {
			data.addData(removedNodes.get(i));
		}
		System.out.println("========== Sending back registred data ==========\n");
		return data;
	}
	
	@Override
	public void sendMessage(Message message) throws RemoteException {
		
		Random r = new Random();
		r.setSeed(System.currentTimeMillis());
		int node1 = Math.abs(r.nextInt())%myNodes.size();
		int node2 = Math.abs(r.nextInt())%myNodes.size();
		
		System.out.println("========== Sending from:\n" + myNodes.get(node1).getId() + "\nto\n" + myNodes.get(node2).getId() + "\nmessage: '" + new String((message.getByte())) + "'==========");
		
		myNodes.get(node1).sendMessage(message, myNodes.get(node2).getId());
		System.out.println("========== Sent " + message + " ==========\n");
	}
	
	public void sendRandom(Integer messageNum) throws RemoteException {
		System.out.println("========== Sending " + messageNum + " random messages ==========");
		
		Random r = new Random();
		r.setSeed(System.currentTimeMillis());
		
		byte[][] m1 = {
				"ALA".getBytes(),
				"MA".getBytes(),
				"KOTA".getBytes(),
				"KOT MA ALE".getBytes(),
				"ZALICZYMY PROJEKT Z TIN! :)".getBytes(),
				"Na pewno zaliczymy projekt z tin...".getBytes(),
				"No moze zaliczymy".getBytes(),
				"Pewnie zaliczymy projekt...".getBytes(),
				"Mam nadzieje, ze zaliczymy".getBytes(),
				"Ciekawe czy zaliczymy".getBytes(),
				"Oj... ciezka noc...".getBytes()
		};
		
		for(int i = 0; i < messageNum; ++i) {
			int node1 = Math.abs(r.nextInt())%myNodes.size();
			int node2 = Math.abs(r.nextInt())%myNodes.size();
			int messInd = Math.abs(r.nextInt())%m1.length;
			System.out.println("<<--sending from:\n" + myNodes.get(node1).getId() + "\nto\n" + myNodes.get(node2).getId() + "\nmessage: '" + new String((m1[messInd])) + "'\n-->>");
		
			myNodes.get(node1).sendMessage(new Message(m1[messInd]), myNodes.get(node2).getId());
		}
		System.out.println("========== Sent " + messageNum + " random messages ==========\n");
	}
	
	
	public ArrayList<NodeId> getNodes() throws RemoteException {
		ArrayList<NodeId> out = new ArrayList<NodeId>();
		for(int i = 0; i < myNodes.size(); ++i) {
			out.add(myNodes.get(i).getId());
		}
		return out;
	}
	
	public Pair<String, String> getEnv(NodeId id) throws RemoteException {
		for(int i = 0; i < myNodes.size(); ++i) {
			if(myNodes.get(i).getId().compareTo(id) == 0) {
				return new Pair<String, String>(myNodes.get(i).getLeafSet().toString(), myNodes.get(i).getRoutingtable().toString());
			}
		}
		return null;
	}

	public static void main(String args[])
	{
		try
		{
			ServerImpl s = new ServerImpl(InetAddress.getLocalHost());
		} 
		catch (Exception e)
		{
			e.printStackTrace();
			System.exit(1);
		}
	}
}
